﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;

namespace NetConsole_2
{
    public partial class frmNetConsole : Form
    {
        public frmNetConsole()
        {
            InitializeComponent();
        }

        const int NET_CONSOLE_PORT = 6666;

        UdpClient udpcNetConsoleUDPClient;
        IPEndPoint ipeIPEndpoint = null;
        bool bNetConsoleListenerRunning = false;

        Thread tNetConsoleUDPReader;
        static bool KeepGoing = true;
        bool UnSaved = true;
        string dateNow = "";
        string dateFormat = "dddMMMd__HH_mm_ss";

        Color colBackground_DotNetError = Color.Red;
        Color colBackground_NetConsoleError = Color.Orange;

        #region AUTOSCROLL STUFF

        // Constants for extern calls to various scrollbar functions
        private const int SB_HORZ = 0x0;
        private const int SB_VERT = 0x1;
        private const int WM_HSCROLL = 0x114;
        private const int WM_VSCROLL = 0x115;
        private const int SB_THUMBPOSITION = 4;
        private const int SB_BOTTOM = 7;
        private const int SB_OFFSET = 13;

        [DllImport("user32.dll")]
        static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int GetScrollPos(IntPtr hWnd, int nBar);
        [DllImport("user32.dll")]
        private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
        [DllImport("user32.dll")]
        static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos);

        #endregion

        void NetConsoleUDP_Read()
        {
            try
            {
                while (KeepGoing)
                {
                    //receive any available NetConsole data
                    byte[] arrNetConsoleData = udpcNetConsoleUDPClient.Receive(ref ipeIPEndpoint);

                    //convert the byte array to a string
                    string strNetConsoleData = Encoding.ASCII.GetString(arrNetConsoleData);

                    if (strNetConsoleData.ToUpper().Contains("ERROR"))
                        AppendNetConsoleErrorToLog(strNetConsoleData);
                    else if (strNetConsoleData.ToUpper().StartsWith("WATCH:"))
                    {
                        dgvWatchVariables.Rows.Add(strNetConsoleData.Substring(6).Split(','));
                    }
                    else
                        AppendToLog(strNetConsoleData);
                }
            }
            catch (ThreadInterruptedException)
            {
                //end the thread (close program)
            }
            catch (SocketException)
            {
                //do not display error message, because the window is most likely gone
            }
            catch (Exception ex)
            {
                SetText(ex.ToString());
            }
        }

        void StartNetConsoleListener(IPAddress IP_Address)
        {
            try
            {
                //create a new IP EndPoint for the specified IP address with the NetConsole port
                ipeIPEndpoint = new IPEndPoint(IP_Address, NET_CONSOLE_PORT);

                //create a new UDP client for receiving NetConsole data
                udpcNetConsoleUDPClient = new UdpClient(NET_CONSOLE_PORT);

                //set the flag indicating that the NetConsole listener is running
                bNetConsoleListenerRunning = true;
            }
            catch (Exception ex)
            {
                AppendDotNetErrorToLog(".NET ERROR: Socket could not be opened. IP: " + IP_Address + " " + ex.ToString());
            }

            //create and start a new thread to continuously receive the NetConsole data
            tNetConsoleUDPReader = new Thread(new ThreadStart(NetConsoleUDP_Read));
            tNetConsoleUDPReader.Start();
        }

        // This delegate enables asynchronous calls for setting
        // the text property on a TextBox control.
        delegate void SetTextCallback(string text);

        private void SetText(string text)
        {
            // InvokeRequired required compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (this.txtConsole.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                //this.txtConsole.AppendText(text);

                #region AUTOSCROLL STUFF (CHECK!!!)

                //no idea if this works... hopefully it will ;)
                //prolly don't need the original, 'this.txtConsole.AppendText(text);' line?
                bool bottomFlag = false;
                int VSmin;
                int VSmax;
                int sbOffset;
                int savedVpos;
                // Make sure this is done in the UI thread
                if (this.txtConsole.InvokeRequired)
                {
                    this.txtConsole.Invoke(new SetTextCallback(SetText), new object[] { text });
                }
                else
                {
                    // Win32 magic to keep the textbox scrolling to the newest append to the textbox unless
                    // the user has moved the scrollbox up
                    sbOffset = (int)((this.txtConsole.ClientSize.Height - SystemInformation.HorizontalScrollBarHeight) / (this.txtConsole.Font.Height));
                    savedVpos = GetScrollPos(this.txtConsole.Handle, SB_VERT);
                    GetScrollRange(this.txtConsole.Handle, SB_VERT, out VSmin, out VSmax);
                    if (savedVpos >= (VSmax - sbOffset - 1))
                        bottomFlag = true;
                    this.txtConsole.AppendText(text + Environment.NewLine);
                    if (bottomFlag)
                    {
                        GetScrollRange(this.txtConsole.Handle, SB_VERT, out VSmin, out VSmax);
                        savedVpos = VSmax - sbOffset;
                        bottomFlag = false;
                    }
                    SetScrollPos(this.txtConsole.Handle, SB_VERT, savedVpos, true);
                    PostMessageA(this.txtConsole.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * savedVpos, 0);
                }

                #endregion

            }

            UnSaved = true;
            if (!this.Text.Contains('*'))
            {
                this.Text += '*';
            }
        }

        private void frmNetConsole_Load(object sender, EventArgs e)
        {
            //TODO - load last used team number
            //TODO - load highlighting settings
        }

        private void frmNetConsole_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (UnSaved)
            {
                DialogResult result = MessageBox.Show("Would you like to save the log before closing?", "Save?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
                if (result == DialogResult.Yes)
                {
                    OpenSaveDlg();
                }
                else if (result == DialogResult.Cancel)
                {
                    e.Cancel = true;
                }
            }

            //TODO
            //udpcNetConsoleUDPClient.Close();
        }

        private void btnClearText_Click(object sender, EventArgs e)
        {
            txtConsole.Clear();
        }

        private void txtConsole_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            //disallow deleting? (preserve the output)
            if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back)
            {
                //but..how?
                
            }

            UnSaved = true;
            if (!this.Text.Contains('*'))
            {
                this.Text += '*';
            }
        }

        private void OpenSaveDlg()
        {
            if (this.Text == "Net Console 2.0" || this.Text == "NetConsole 2.0*")
            {
                if (!chkLogStart.Checked)
                {
                    DateTime time = DateTime.Now;
                    dateNow = time.ToString(dateFormat);
                }
                else
                {
                    fileSave.FileName = dateNow;
                }
            }
            else
            {
                string temp = this.Text.Remove(0, 18);
                if (temp.Contains('*')) temp = temp.Remove(temp.Length - 5, 5);
                fileSave.FileName = temp;
            }

            fileSave.ShowDialog(this);
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            OpenSaveDlg();
        }

        private void fileSave_FileOk(object sender, CancelEventArgs e)
        {
            string name = fileSave.FileName;
            File.WriteAllText(name, txtConsole.Text);
            UnSaved = false;
            this.Text = "Net Console 2.0 - " + Path.GetFileName(name);
        }

        public static FileInfo GetNewestFile(DirectoryInfo directory)
        {
            return directory.GetFiles()
                .Union(directory.GetDirectories().Select(d => GetNewestFile(d)))
                .OrderByDescending(f => (f == null ? DateTime.MinValue : f.LastWriteTime))
                .FirstOrDefault();
        }

        private void btnLoad_Click(object sender, EventArgs e)
        {
            FileInfo newest = GetNewestFile(new DirectoryInfo(fileOpen.InitialDirectory));
            fileOpen.FileName = newest.Name;
            fileOpen.ShowDialog(this);
        }

        private void fileOpen_FileOk(object sender, CancelEventArgs e)
        {
            string name = fileOpen.FileName;
            txtConsole.Text = File.ReadAllText(name);
            this.Text = "Net Console 2.0 - " + Path.GetFileName(name);
        }

        private void chkLogStart_CheckedChanged(object sender, EventArgs e)
        {
            if (chkLogStart.Checked)
            {
                DateTime time = DateTime.Now;
                dateNow = time.ToString(dateFormat);
            }
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            //disable the connect button
            btnConnect.Enabled = false;

            try
            {
                //generate the octets for the IP address
                byte cOctet3 = 10;
                byte cOctet2 = (byte)Math.Floor(nudTeamNumber.Value / 100);
                byte cOctet1 = (byte)((int)(nudTeamNumber.Value) - (cOctet2 * 100));
                byte cOctet0 = 2;

                //IP Address = cOctet3.cOctet2.cOctet1.cOctet0
                byte[] arrIPAddress = { cOctet3, cOctet2, cOctet1, cOctet0 };

                //start the NetConsole listener with the specified IP address
                StartNetConsoleListener(new IPAddress(arrIPAddress));

                //enable the disconnect button
                btnDisconnect.Enabled = true;
            }
            catch (Exception ex)
            {
                AppendDotNetErrorToLog(".NET ERROR: Error connecting. " + ex.ToString());

                //make sure we re-enable the connect button and disable the disconnect button
                btnDisconnect.Enabled = false;
                btnConnect.Enabled = true;
            }
        }

        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            //disable the disconnect button
            btnDisconnect.Enabled = false;

            try
            {
                //TODO
                if (bNetConsoleListenerRunning)
                    udpcNetConsoleUDPClient.Close();

                //enable the connect button
                btnConnect.Enabled = true;
            }
            catch (Exception ex)
            {
                AppendDotNetErrorToLog(".NET ERROR: Error disconnecting. " + ex.ToString());

                //make sure we re-enable the connect button and disable the disconnect button
                btnDisconnect.Enabled = false;
                btnConnect.Enabled = true;
            }
        }

        // This delegate enables asynchronous calls for setting
        // the text property on a TextBox control.
        delegate void AppendToLogCallback(string text, Color BackColor);

        private void AppendToLog(string Text, Color BackColor)
        {
            // InvokeRequired required compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (this.txtConsole.InvokeRequired)
            {
                AppendToLogCallback d = new AppendToLogCallback(AppendToLog);
                this.Invoke(d, new object[] { Text, BackColor });
            }
            else
            {
                txtConsole.AppendText(Text);
                txtConsole.Select(txtConsole.Text.Length - Text.Length, Text.Length);
                txtConsole.SelectionBackColor = BackColor;
            }
        }

        private void AppendDotNetErrorToLog(string Text)
        {
            if (tspHighlightDotNetErrors.Checked)
                AppendToLog(Text, colBackground_DotNetError);
            else
                AppendToLog(Text);
        }

        private void AppendNetConsoleErrorToLog(string Text)
        {
            if (tspHighlightNetConsoleErrors.Checked)
                AppendToLog(Text, colBackground_NetConsoleError);
            else
                AppendToLog(Text);
        }

        private void AppendToLog(string Text)
        {
            AppendToLog(Text, txtConsole.BackColor);
        }
    }
}
